﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace LucyGrid
{
	/// <summary>
	/// Simple webserver
	/// <remarks>http://onehourofdevelopment.blogspot.com.es/2013/05/a-very-simple-webserver-using-nets.html
	/// TODO: 
	///		* ThreadPool vs Tasks
	///		* Add Rx https://github.com/Anna-Rx/Anna
	/// </remarks>
	/// </summary>
	public class GridEndpoint
	{
		public delegate void RequestReceivedHandler(object sender, HttpListenerContext context);
		public event RequestReceivedHandler RequestReceived;

		private readonly HttpListener _listener;
		Exception _startException = null;
		List<String> _requests;

		static GridEndpoint() {
			GridComm.Initialize();
		}

		public GridEndpoint(string prefix)
		{
			_listener = new HttpListener();
			_listener.Prefixes.Add(prefix);
			_requests = new List<string>();
		}

		public int RequestCount {
			get {
				return _requests.Count;
			}
		}

		public void Start()
		{
			try
			{
				_listener.Start();
				GridTask.Run(Listen);
			}
			catch (Exception ex)
			{
				_startException = ex;
			}
		}

		public void Stop()
		{
			_listener.Stop();
		}

		private void Listen()
		{
			Thread.CurrentThread.Name = string.Format("GRIDENDPOINT LISTEN-{0}", Thread.CurrentThread.ManagedThreadId);
			try
			{
				while (_listener.IsListening)
					_listener.BeginGetContext((ar) =>
					{
						ProcessContext(ar);
					}, _listener).AsyncWaitHandle
					// WARNING: What about long running processes?
					.WaitOne(10000);
			}
			catch (Exception ex) {
				GridUtils.NotifyLog("ENDPOINT,ERROR", "Error processing request context: {0}", ex.ToString());
			}
		}

		private void ProcessContext(IAsyncResult ar)
		{
			try
			{
				var listener = (HttpListener)ar.AsyncState;
				var context = listener.EndGetContext(ar);
				// http://blog.stephencleary.com/2013/11/there-is-no-thread.html
				GridTask.Run(() =>
				{
					Thread.CurrentThread.Name = string.Format("GRIDENDPOINT PROCESSREQ-{0}", Thread.CurrentThread.ManagedThreadId);
					ProcessRequest(context);
				});
			}
			catch (Exception ex)
			{
				GridUtils.NotifyLog("ENDPOINT,ERROR", "Error in request context: {0}", ex.ToString());
			}
		}

		void ProcessRequest(HttpListenerContext context)
		{
			var url = context.Request.Url.ToString();
			lock (_requests)
				_requests.Add(url);
			GridUtils.NotifyLog("ENDPOINT,INFO", "Endpoint request number: {0}", _requests.Count);
			try
			{
				OnRequestReceived(context);
			}
			catch (Exception ex) {
				GridUtils.NotifyLog("ENDPOINT,INFO", "Error procesing request {0}: {1}", url, ex);
				// Try to release response
				FinalizeContext(context);
			}
			lock (_requests)
				_requests.Remove(url);
			
		}

		/// <summary>
		/// In case of error tries to properly finilize the context
		/// </summary>
		/// <param name="context"></param>
		private void FinalizeContext(HttpListenerContext context)
		{
			GridUtils.NotifyLog("ENDPOINT,INFO", "Error procesing request, ensure context is properly released");
			try
			{
				// Make sure Status code is not 200
				if (context.Response.StatusCode == (int)HttpStatusCode.OK)
					context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
				// Make sure InputStream has been release
				try{
					context.Request.InputStream.Dispose();
				}finally{}
				// Make sure OutputStream has been release
				try
				{
					context.Response.OutputStream.Dispose();
				}
				finally { }
			}
			catch (Exception ex) {
				GridUtils.NotifyLog("ENDPOINT,ERROR", "FinalizeContext {0}", ex);
			}
				
		}

		private void OnRequestReceived(HttpListenerContext context)
		{
			try
			{
				var url = context.Request.Url.ToString();
				GridUtils.NotifyLog("ENDPOINT,INFO", "Request received from {0} {1}, url {2}", context.Request.RemoteEndPoint.Address.ToString(), context.Request.Headers["id"], url);
				if (RequestReceived != null) RequestReceived(this, context);
			}
			catch (Exception ex)
			{
				try
				{
					context.Response.OutputStream.Dispose();
				}
				catch (Exception inner) {
					GridUtils.NotifyLog("ENDPOINT,ERROR", "OnRequestReceived Mortal! {0}", inner);
				}
				GridUtils.NotifyLog("ENDPOINT,ERROR", "Error procesing request {0} from {1}: {2}", context.Request.Url, context.Request.RemoteEndPoint.Address.ToString(),
					ex.ToString());
			}
		}
	}
}
